package se

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/oils/sets"
	"gitee.com/xuender/oils/slices"
	"gitee.com/xuender/oils/tags"
	"gitee.com/xuender/se/pb"
	"github.com/syndtr/goleveldb/leveldb/iterator"
	"github.com/syndtr/goleveldb/leveldb/util"
)

// Index 索引文件或目录.
func (p *Service) Index(path string) {
	if p.ignore(path) {
		logs.Warn("忽略:", path)

		return
	}

	abs := logs.PanicString(filepath.Abs(path))
	info, change := p.fs.File(abs)

	if info.IsDir {
		key := []byte{ByteFile, ByteIndex}
		is := []string{}
		_ = p.ls.Get(key, &is)
		ss := sets.NewStrings(is...)

		ss.Add(info.Name)
		p.ls.Put(key, ss.Slice())
	}

	if change {
		if info.IsDir {
			logs.Panic(filepath.Walk(info.Name, p.walk))

			return
		}

		p.index(info.Name, info.Id)
	}
}

// Indexes 索引目录列表.
func (p *Service) Indexes() []string {
	key := []byte{ByteFile, ByteIndex}
	is := []string{}
	_ = p.ls.Get(key, &is)

	return is
}

func (p *Service) ignore(path string) bool {
	dirs := strings.Split(path, string(os.PathSeparator))
	for _, dir := range dirs {
		if dir != "" && dir != "." && dir[0] == '.' {
			return true
		}
	}

	return slices.Strings(p.config.Ignore).Any(dirs...)
}

func (p *Service) walk(path string, info os.FileInfo, err error) error {
	if p.ignore(path) {
		return nil
	}

	if info.IsDir() {
		return nil
	}

	ext := filepath.Ext(path)
	if !slices.Strings(p.config.Exts).Has(ext) {
		return nil
	}

	logs.Debug("walk:", path)

	i, change := p.fs.file(path, info)
	if change && !i.IsDir {
		p.index(i.Name, i.Id)
	}

	return nil
}

func (p *Service) index(path string, fid uint32) {
	bs := logs.PanicBytes(ioutil.ReadFile(path))
	str := string(bs)
	set := Split(str)
	str = strings.ToLower(strings.ReplaceAll(str, "_", ""))
	ts := tags.NewTags()

	for s := range set {
		ts.Add(p.ws.Word(s, true))
	}
	// 分析可能包含的词组，文件寻找词组
	for words := range p.ws.words {
		wordsSet, has := p.ws.wordsMap[words]

		if !has {
			wordsSet := sets.NewStrings()

			split(wordsSet, words)
			p.ws.wordsMap[words] = wordsSet
		}

		if set.AllStrings(wordsSet) {
			for _, w := range WordsMay(words) {
				if strings.Contains(str, w) {
					ts.Add(p.ws.Word(words, true))

					break
				}
			}
		}
	}

	p.fs.Tag(fid, ts)
}

// Reindex 重新索引.
func (p *Service) Reindex() {
	iter := p.ls.DB.NewIterator(util.BytesPrefix(p.fs.fileIDKey[:]), nil)

	for iter.Next() {
		of := &pb.File{}
		if !p.checkFile(of, iter) {
			continue
		}

		nf, err := os.Stat(of.Name)
		if os.IsNotExist(err) {
			p.fs.Delete(of.Name)

			continue
		}

		logs.Panic(err)

		if !of.IsDir && nf.IsDir() == of.IsDir && nf.ModTime().UnixNano() == of.ModTime && nf.Size() == of.Size {
			logs.Debug("文件忽略", of.Name)

			continue
		}

		logs.Info("重新索引:", of.Name)
		p.Index(of.Name)
	}
}

func (p *Service) checkFile(of *pb.File, iter iterator.Iterator) bool {
	fileIDKey := []byte{}
	if !p.ls.Get(iter.Key(), &fileIDKey) {
		return false
	}

	if !p.ls.Get(fileIDKey, of) {
		return false
	}

	return true
}
